package com.taimi.springbootzendaodemo.edition;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import javax.annotation.PostConstruct;

import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.DomNodeList;
import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput;
import com.gargoylesoftware.htmlunit.html.HtmlTextInput;
import com.gargoylesoftware.htmlunit.util.Cookie;
import com.taimi.springbootzendaodemo.system.EnvironmentUtils;
import com.taimi.springbootzendaodemo.system.ZendaoProperties;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class GetEditionInfo {

    public String LOGIN_PAGE;

    @Autowired
    private WebClient webClient;

    @Autowired
    private ZendaoProperties zendaoProperties;

    @Autowired
    private EnvironmentUtils envUtils;

    private final StopWatch stopWatch = new StopWatch();

    @PostConstruct
    public void init() throws URISyntaxException {

        URI url = URI.create(zendaoProperties.getUrl());
        URI targetUri = new URI(url.getScheme(), null, url.getHost(), url.getPort(), "/pro/user-login.html", null,
                null);
        LOGIN_PAGE = targetUri.toString();
    }

    public VersionInfo getVersionInfo(final String targetPage) throws IOException {
        login(webClient);
        stopWatch.start("get buildViewPage");
        final HtmlPage buildViewPage = webClient.getPage(targetPage);
        stopWatch.stop();
        stopWatch.start("buildInfos");
        final BuildInfo buildInfos = buildInfos(buildViewPage);
        stopWatch.stop();
        log.info("build info: {}", buildInfos);
        stopWatch.start("bugList");
        final List<Bug> bugs = getFixedBugs(buildViewPage);
        stopWatch.stop();
        bugs.forEach(bug -> {
            log.info("bug: {}", bug);
        });
        stopWatch.start("storyList");
        final List<Story> stories = stories(buildViewPage);
        stopWatch.stop();
        for (final Story story : stories) {
            log.info("story: {}", story);
        }
        stopWatch.start("newBugs");
        final List<Bug> newBugs = getNewBugs(buildViewPage);
        stopWatch.stop();
        for (final Bug newbug : newBugs) {
            log.info("new bug: {}", newbug);
        }
        log.info("\n\r" + stopWatch.prettyPrint());
        return VersionInfo.builder().bugs(bugs).newBugs(newBugs).stories(stories).buildInfo(buildInfos).build();
    }

    public void login(final WebClient webClient) throws IOException {
        if (zendaoProperties.getCookie().isEnable()) {
            Collection<Cookie> cookies = getCookies();
            final CookieManager cookieManager = webClient.getCookieManager();
            // authenticate cookies and login
            if (cookies.isEmpty()) {// or cookies are expired
                log(webClient);                
                cookies = cookieManager.getCookies();
                // saveCookies
                log.info("saveCookies");
                saveCookie(cookies);
            } else {
                // 添加已存在的 cookie 凭证
                cookies.forEach(cookieManager::addCookie);
            }
        }
        else{
            log(webClient);
        }
    }

    public void log(final WebClient webClient) throws IOException{
        final HtmlPage page = webClient.getPage(LOGIN_PAGE);
        final List<HtmlForm> forms = page.getForms();
        final HtmlForm form = forms.get(0);
        final HtmlTextInput accountInput = form.getInputByName("account");
        accountInput.type(zendaoProperties.getUsername());
        final HtmlPasswordInput passwordInput = form.getInputByName("password");
        passwordInput.type(zendaoProperties.getPassword());
        final HtmlCheckBoxInput keepLoginInput = form.getInputByName("keepLogin[]");
        keepLoginInput.click();
        final DomElement submitButton = page.getElementById("submit");

        HtmlPage loginReturnPage = submitButton.click();
        synchronized (loginReturnPage) {
            try {
                loginReturnPage.wait(TimeUnit.SECONDS.toMillis(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public BuildInfo buildInfos(final HtmlPage buildViewPage) {
        final DomNodeList<DomNode> trs = buildViewPage.querySelectorAll("#buildInfo tbody tr");
        String xpath = "td";
        final String product = ((HtmlElement) trs.get(0).getFirstByXPath(xpath)).getTextContent().trim();
        final String branch = ((HtmlElement) trs.get(1).getFirstByXPath(xpath)).getTextContent().trim();
        final String name = ((HtmlElement) trs.get(2).getFirstByXPath(xpath)).getTextContent().trim();
        final String creator = ((HtmlElement) trs.get(3).getFirstByXPath(xpath)).getTextContent().trim();
        final String packageDate = ((HtmlElement) trs.get(4).getFirstByXPath(xpath)).getTextContent().trim();
        final String sourceAddress = ((HtmlElement) trs.get(5).getFirstByXPath(xpath)).getTextContent().trim();
        final String downloadAddress = ((HtmlElement) trs.get(6).getFirstByXPath(xpath)).getTextContent().trim();
        final String description = ((HtmlElement) trs.get(7).getFirstByXPath(xpath)).getTextContent().trim();
        return BuildInfo.builder().product(product).branch(branch).name(name).creator(creator).packageDate(packageDate)
                .sourceAddress(sourceAddress).downloadAddress(downloadAddress).description(description).build();
    }

    public List<Bug> getFixedBugs(final HtmlPage buildViewPage) {
        return encapsulate(buildViewPage, tr -> {
            final DomNodeList<DomNode> tds = tr.querySelectorAll("td");
            final String bugId = tds.get(0).getTextContent().trim();
            final String title = tds.get(1).getTextContent().trim();
            return Bug.builder().bugId(bugId).title(title).build();
        }, "#bugList tbody tr");
    }

    public List<Story> stories(final HtmlPage buildViewPage) {
        return encapsulate(buildViewPage, tr -> {
            final DomNodeList<DomNode> tds = tr.querySelectorAll("td");
            final String storyId = tds.get(0).getTextContent().trim();
            final String priority = tds.get(1).getTextContent().trim();
            final String storyName = tds.get(2).getTextContent().trim();
            final String creator = tds.get(3).getTextContent().trim();
            return Story.builder().storyId(storyId).priority(priority).storyName(storyName).creator(creator).build();
        }, "#storyList tbody tr");
    }

    public List<Bug> getNewBugs(final HtmlPage buildViewPage) {
        return encapsulate(buildViewPage, tr -> {
            final DomNodeList<DomNode> tds = tr.querySelectorAll("td");
            final String bugId = tds.get(0).getTextContent().trim();
            final String level = tds.get(1).getTextContent().trim();
            final String title = tds.get(2).getTextContent().trim();
            return Bug.builder().bugId(bugId).title(title).level(level).build();
        }, "#newBugs tbody tr");
    }

    public <T> List<T> encapsulate(final HtmlPage buildViewPage, final Function<DomNode, T> function,
            final String querySelector) {
        final List<DomNode> trs = buildViewPage.querySelectorAll(querySelector);
        final List<T> objects = new ArrayList<>(trs.size());
        for (final DomNode tr : trs) {
            objects.add(function.apply(tr));
        }
        return objects;
    }

    private static final File COOKIE_FILE = Paths.get(".", "zendaocookies").toFile();

    @SuppressWarnings("unchecked")
    private Collection<Cookie> getCookies() {
        log.info("getCookies");
        if (!COOKIE_FILE.exists()) {
            return Collections.emptyList();
        }
        try (final ObjectInputStream objectOutputStream = new ObjectInputStream(new FileInputStream(COOKIE_FILE))) {
            return (Collection<Cookie>) objectOutputStream.readObject();
        } catch (final Exception e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    private void saveCookie(final Collection<Cookie> cookies) throws IOException {
        if (envUtils.isTest()) {
            cookies.forEach(cookie -> log.info(cookie.toString()));
        }
        try (final ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(COOKIE_FILE))) {
            objectOutputStream.writeObject(cookies);
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }

}